<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Custom widget in Qyoto</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="GUI, Qyoto, Qt, custom widget, C#, learn Qyoto">
<meta name="description" content="This chapter of the Qyoto tutorial is dedicated to
creating custom widgets.">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Custom widget in Qyoto</h1>

<p>
In this part of the Qyoto C# programming tutorial, we will show how to create 
a custom widget. 
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "ca-pub-9706709751191532";
/* top_horizontal */
google_ad_slot = "3327173442";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<p>
Most toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. 
No toolkit can provide all possible widgets. Programmers must create such widgets by themselves. 
They do it by using the drawing tools provided by the toolkit. There are two possibilities. 
A programmer can modify or enhance an existing widget. Or he can create a custom widget from scratch. 
</p>

<p>
In this chapter we will create two custom widgets. A Buring widget and a Led widget. 
</p>


<h2>The Burning widget</h2>

<p>
In the next example, we will create a custom burning widget. 
This widget can be seen in applications like Nero or K3B. The widget 
will be created from scratch. 
</p>

<div class="codehead">burning.cs</div>
<pre class="code">
using System;
using QtCore;
using QtGui;

public class Burning : QWidget 
{
    const int PANEL_HEIGHT = 30;
    const int DISTANCE = 19;
    const int LINE_WIDTH = 5;
    const int DIVISIONS = 10;
    const float FULL_CAPACITY = 700f;
    const float MAX_CAPACITY = 750f;

    QColor redColor = new QColor(255, 175, 175);
    QColor yellowColor = new QColor(255, 255, 184);

    QWidget parent;

    String[] num = 
    {
        "75", "150", "225", "300",
        "375", "450", "525", "600",
        "675"
    };


    public Burning(QWidget parent) 
    {
        this.parent = parent;
        MinimumHeight = PANEL_HEIGHT;
    }

    protected override void OnPaintEvent(QPaintEvent pe) 
    {
        QPainter ptr = new QPainter(this);
        
        DrawWidget(ptr);
        ptr.End();
    }

    void DrawWidget(QPainter ptr) 
    {
        QyotoApp burn = (QyotoApp) parent;

        float slid_width = burn.GetCurrentWidth();
        float width = Size.Width;
        float step = width / DIVISIONS;

        float till = (width / MAX_CAPACITY) * slid_width;
        float full = (width / MAX_CAPACITY) * FULL_CAPACITY;

        if (slid_width > FULL_CAPACITY) 
        {
            ptr.Pen = yellowColor;
            ptr.Brush = yellowColor;
            ptr.DrawRect(new QRectF(0, 0, full, PANEL_HEIGHT));
            ptr.Pen = redColor;
            ptr.Brush = redColor;
            ptr.DrawRect(new QRectF(full+1, 0, till-full, PANEL_HEIGHT));

        } else 
        {
            if (slid_width > 0) 
            {
               ptr.Pen = yellowColor;
               ptr.Brush = yellowColor;
               ptr.DrawRect(new QRectF(0, 0, till, PANEL_HEIGHT));
            }
        }

        ptr.Pen = new QColor(90, 90, 90);
        ptr.SetBrush(BrushStyle.NoBrush);
        ptr.DrawRect(0, 0, Size.Width-1, PANEL_HEIGHT-1);
        
        QFont newFont = ptr.Font;
        newFont.PointSize = 7;
        ptr.Font = newFont;

        QFontMetrics metrics = new QFontMetrics(newFont);

        for (int i = 1; i &lt;= num.Length; i++) 
        {
            ptr.DrawLine(new QLineF(i*step, 1, i*step, LINE_WIDTH));

            int w = metrics.Width(num[i-1]);
            ptr.DrawText(new QPointF(i*step-w/2, DISTANCE), num[i-1]);
        }        
    }
}
</pre>

<p>
In this file, we create the Burning widget. 
</p>


<pre class="explanation">
public class Burning : QWidget 
{
...
</pre>

<p>
The custom widget is based on the <code>QWidget</code> class. 
</p>

<pre class="explanation">
const int PANEL_HEIGHT = 30;
const int DISTANCE = 19;
const int LINE_WIDTH = 5;
const int DIVISIONS = 10;
const float FULL_CAPACITY = 700f;
const float MAX_CAPACITY = 750f;
</pre>

<p>
These are important constants. The <code>PANEL_HEIGHT</code> defines the height
for the custom widget. 
The <code>DISTANCE</code> is the distance of the numbers on the scale from the top
of their parent border. The <code>LINE_WIDTH</code> is the vertical line width.
The <code>DIVISIONS</code> is the number of parts of the
scale. The <code>FULL_CAPACITY</code> is the capacity of the media. After
it is reached, overburning happens. This is visualized by a red colour. 
The <code>MAX_CAPACITY</code> is the maximum capacity of a medium. 
</p>

<pre class="explanation">
String[] num = 
{
    "75", "150", "225", "300",
    "375", "450", "525", "600",
    "675"
};
</pre>

<p>
We use these numbers to build the scale of the Burning widget. 
</p>

<pre class="explanation">
protected override void OnPaintEvent(QPaintEvent pe) 
{
    QPainter ptr = new QPainter(this);
    
    DrawWidget(ptr);
    ptr.End();
}
</pre>

<p>
The drawing of the custom widget is delegated to the 
<code>DrawWidget()</code> method.
</p>

<pre class="explanation">
QyotoApp burn = (QyotoApp) parent;
</pre>

<p>
We retrieve the reference to the parent widget. 
</p>

<pre class="explanation">
float slid_width = burn.GetCurrentWidth();
</pre>

<p>
We use it to get the currently selected slider value.
</p>

<pre class="explanation">
float width = Size.Width;
</pre>

<p>
We get the width of the widget. The width of the custom widget is dynamic. It can be
resized by a user. 
</p>

<pre class="explanation">
float till = (width / MAX_CAPACITY) * slid_width;
float full = (width / MAX_CAPACITY) * FULL_CAPACITY;
</pre>

<p>
We use the <code>width</code> variable to do the transformations. Between 
the values of the scale and the custom widget's measures. Note that we use floating
point values. We get greater precision in drawing. 
</p>

<pre class="explanation">
ptr.Pen = redColor;
ptr.Brush = redColor;
ptr.DrawRect(new QRectF(full+1, 0, till-full, PANEL_HEIGHT));
</pre>

<p>
These three lines draw the red rectangle, indicating the overburning. 
</p>

<pre class="explanation">
ptr.DrawRect(0, 0, Size.Width-1, PANEL_HEIGHT-1);
</pre>

<p>
This is the perimeter of the widget. The outside rectangle. 
</p>

<pre class="explanation">
ptr.DrawLine(new QLineF(i*step, 1, i*step, LINE_WIDTH));
</pre>

<p>
Here we draw the small vertical lines. 
</p>

<pre class="explanation">
QFontMetrics metrics = new QFontMetrics(newFont);
...
int w = metrics.Width(num[i-1]);
ptr.DrawText(new QPointF(i*step-w/2, DISTANCE), num[i-1]);
</pre>

<p>
Here we draw the numbers on the scale. To precisely position the numbers,
we must get the width of the string. 
</p>

<div class="codehead">main.cs</div>
<pre class="code">
using System;
using QtCore;
using QtGui;

/**
 * ZetCode Qyoto C# tutorial
 *
 * In this program, we create
 * a custom Burning widget.
 *
 * @author Jan Bodnar
 * website zetcode.com
 * last modified October 2012
 */

public class QyotoApp : QWidget 
{
    const int MAX_CAPACITY = 750;

    QSlider slider;
    QWidget widget;
    int cur_width;

    public QyotoApp() 
    {
        WindowTitle = "The Burning Widget";

        InitUI();

        Resize(370, 200);
        Move(300, 300);
        Show();
    }

    void InitUI() 
    {       
       slider = new QSlider(Qt.Orientation.Horizontal , this);
       slider.Maximum = MAX_CAPACITY;
       slider.SetGeometry(50, 50, 130, 30);

       slider.ValueChanged += OnValueChanged;
       
       QVBoxLayout vbox = new QVBoxLayout(this);
       QHBoxLayout hbox = new QHBoxLayout();

       vbox.AddStretch(1);

       widget = new Burning(this);
       hbox.AddWidget(widget, 0);

       vbox.AddLayout(hbox);

       Layout = vbox;
    }

    [Q_SLOT]
    void OnValueChanged(int val) 
    {
        cur_width = val;
        widget.Repaint();
    }

    public int GetCurrentWidth() 
    {
        return cur_width;
    }

    [STAThread]
    public static int Main(String[] args) 
    {
        new QApplication(args);
        new QyotoApp();
        return QApplication.Exec();
    }
}
</pre>

<p>
This is the main file. Here we create the slider widget and use our
custom widget. 
</p>

<pre class="explanation">
widget = new Burning(this);
hbox.AddWidget(widget, 0);
</pre>

<p>
We create the instance of the Burning widget and add
it to the horizontal box. 
</p>

<pre class="explanation">
[Q_SLOT]
void OnValueChanged(int val) 
{
    cur_width = val;
    widget.Repaint();
}
</pre>

<p>
When the value of the slider changes, we store it inside the
cur_width variable and repaint the custom widget. 
</p>

<pre class="explanation">
public int GetCurrentWidth() 
{
    return cur_width;
}
</pre>

<p>
This method is called by the custom widget to get the actual slider
value. 
</p>


<img src="/img/gui/sharpqyoto/burning.png" alt="The Burning widget">
<div class="figure">Figure: The Burning widget</div>


<h2>The Led widget</h2>

<p>
A Led widget is a bulb, which can be set to different colours. In our case red,
green, orange and black. This custom widget is simply created with a SVG
image. There are four SVG images. Each for one state of the Led widget.
</p>

<p>
To compile the example, we need to reference the qyoto-qtsvg.dll library.
For example, on our system we have added -r:/usr/local/lib/mono/qyoto/qyoto-qtsvg.dll
to the compile options.
</p>

<div class="codehead">led.cs</div>
<pre class="code">
using System;
using QtCore;
using QtGui;
using QtSvg;

public class Led : QWidget 
{
    string[] cols;
    int col;

    public Led(QWidget parent) 
    {
        const int GREEN = 1;
        col = GREEN;

        SetMinimumSize(50, 50);
        SetMaximumSize(50, 50);
        
        cols = new string[] { "red.svg", "green.svg",  
            "orange.svg", "black.svg" };
    }

    public void SetColour(int newColour)
    {
        col = newColour;
        Update();
    }

    protected override void OnPaintEvent(QPaintEvent e) 
    {
        QPainter ptr = new QPainter(this);
        
        DrawCustomWidget(ptr);
        ptr.End();
    }

    void DrawCustomWidget(QPainter ptr) 
    {
        QSvgRenderer srnd = new QSvgRenderer();
        srnd.Load(cols[col]);
        srnd.Render(ptr);       
    }
}
</pre>

<p>
In the led.cs class we build the custom widget. 
</p>

<pre class="explanation">
const int GREEN = 1;
col = GREEN;
</pre>

<p>
The GREEN constant indicates the state of the Led widget, when all is OK.
The colour variable defnes the current state of the widget.
</p>

<pre class="explanation">
SetMinimumSize(50, 50);
SetMaximumSize(50, 50);
</pre>

<p>
These two lines force the widget to have constant size. The size of a SVG image
is 50x50.
</p>

<pre class="explanation">
cols = new string[] { "red.svg", "green.svg",  
    "orange.svg", "black.svg" };
</pre>

<p>
We store the SVG image file names in the cols array. 
</p>

<pre class="explanation">
public void SetColour(int newColour)
{
    col = newColour;
    Update();
}
</pre>

<p>
The SetColour() method sets the col variable to a new value. 
We call the Update() method to redraw the Led widget to reflect
the new state.
</p>

<pre class="explanation">
void DrawCustomWidget(QPainter ptr) 
{
    QSvgRenderer srnd = new QSvgRenderer();
    srnd.Load(cols[col]);
    srnd.Render(ptr);       
}
</pre>

<p>
In the DrawCustomWidget() method we display the SVG image using
the <code>QSvgRenderer</code> class. 
</p>

<div class="codehead">main.cs</div>
<pre class="code">
using Qyoto;
using QtCore;
using QtGui;

/**
 * ZetCode Qyoto C# tutorial
 *
 * This program creates a custom Led
 * widget.
 *
 * @author Jan Bodnar
 * website zetcode.com
 * last modified October 2012
 */


public class QyotoApp : QWidget 
{
    const int RED = 0;
    const int GREEN = 1;
    const int ORANGE = 2;
    const int BLACK = 3;
    
    Led led;

    public QyotoApp() 
    {
        WindowTitle = "Led widget";

        SetupUI();

        Resize(250, 150);
        Move(300, 300);
        Show();
    }

    public void SetupUI() 
    {        
        QVBoxLayout vbox = new QVBoxLayout();
        QHBoxLayout hbox = new QHBoxLayout();
        
        led = new Led(this);

        hbox.AddWidget(led);
        vbox.AddStretch(1);
        vbox.AddLayout(hbox);
        vbox.AddStretch(1);
        
        QHBoxLayout hbox2 = new QHBoxLayout();

        QPushButton pb1 = new QPushButton("Normal", this);
        QPushButton pb2 = new QPushButton("Warning", this);
        QPushButton pb3 = new QPushButton("Emergency", this);
        QPushButton pb4 = new QPushButton("Off", this);
        
        hbox2.AddWidget(pb1);
        hbox2.AddWidget(pb2);
        hbox2.AddWidget(pb3);
        hbox2.AddWidget(pb4);
                
        vbox.AddLayout(hbox2);
        
        Connect(pb1, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
        Connect(pb2, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
        Connect(pb3, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
        Connect(pb4, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
       
        Layout = vbox;
    }
       
    [Q_SLOT]
    public void OnClicked() 
    {
   
        QPushButton sender = (QPushButton) this.Sender();
        string text = sender.Text;

        if (text == "Normal") 
        {
            led.SetColour(GREEN);
        } 
        else if (text == "Warning")
        {
            led.SetColour(ORANGE);
        } 
        else if (text == "Emergency")
        {
            led.SetColour(RED);
        } 
        else if (text == "Off")
        {
            led.SetColour(BLACK);        
        }
    }

    [STAThread]
    public static int Main(String[] args) 
    {
        new QApplication(args);
        new QyotoApp();

        return QApplication.Exec();
    }
}
</pre>

<p>
This is main.cs file. We have four buttons in a row and a Led widget 
in the center of the window. These four buttons control the state 
of the Led widget. There are four states for a Led widget. Normal, 
warning, emergency and off.
</p>

<pre class="explanation">
led = new Led(this);

hbox.AddWidget(led);
</pre>

<p>
We create an instance of the Led widget and put it into a
horizontal box. 
</p>

<pre class="explanation">
else if ( text == "Warning")
{
    led.SetColour(ORANGE);
} 
</pre>

<p>
If we click on the Warning button, the colour of the Led
widget will change to orange. More precisely a new orange SVG
image is loaded and displayed. 
</p>


<img src="/img/gui/sharpqyoto/ledwidget.png" alt="The Led widget">
<div class="figure">Figure: The Led widget showing the Off state</div>

<p>
In this part of the Qyoto C# tutorial, we have demonstrated how to create
custom widgets. 
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified November 1, 2012  <span class="copyright">&copy; 2007 - 2013 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>
